#include <iostream>
#include <unistd.h>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <functional>
#include <cstdlib>
#include <cassert>
#include <ctime>

#define PROCESS_NUM 5
#define MakeSeed() srand((unsigned int)time(nullptr) ^ getpid() ^ 0x32234114 ^ rand() % 3214)
// time_t long int
// 让rand()产生的随机数更为随机一些，我们设置一个较为随机的srand()随机数生成器的种子

///////////////////////下面的代码是子进程需要完成的任务 -- 模拟一下////////////////////////////

// typedef std::function<void ()> func;
typedef void (*func_t)(); // 函数指针类型void(*)() --> 重定义为func_t类型

void downLoadTask()
{
    std::cout << getpid() << ": 下载任务\n"
              << std::endl;
    sleep(1);
}

void IOTask()
{
    std::cout << getpid() << ": IO任务\n"
              << std::endl;
    sleep(1);
}

void flushTask()
{
    std::cout << getpid() << ": 刷新任务\n"
              << std::endl;
    sleep(1);
}

void loadTaskFunc(std::vector<func_t> *out)
{
    assert(out);
    out->push_back(downLoadTask);
    out->push_back(IOTask);
    out->push_back(flushTask);
}
///////////////////////下面的代码是一个多进程的程序////////////////////////////
class subEp// EndPoint 另一端
{
public:
    subEp(pid_t subID, int writefd)
        : _subID(subID), _writefd(writefd)
    {
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "process-%d[pid(%d)fd(%d)]", _num++, _subID, _writefd);
        _name = buffer;
    }

public:
    static int _num;
    std::string _name;
    pid_t _subID;
    int _writefd; // 父进程通过管道文件的写端给子进程发送command code 
};
int subEp::_num = 0;

int recvTask(int readFd)
{
    int code = 0;
    //************只有在所有写端都关闭的情况下，读端才会返回0。否则如果写端不关闭并且不写入数据，则读端会一直阻塞等待***************
    ssize_t s = read(readFd, &code, sizeof(code));
    // assert(s == sizeof(int)); // 父进程退出，写端关闭，读端读到为0，断言失败。若为读关闭，写进程被kill。
    if (s == 4)
        return code;
    else if (s <= 0)
        return -1;//command code返回-1，让子进程退出吧，不要再继续阻塞等待父进程发送任务了，因为父进程已经关闭wfd了
}
void createsubprocess(std::vector<subEp> *out, std::vector<func_t> &funcMap) // 这里以输出型参数的方式将结果带出去，改变vector
{
    std::vector<int> deleteFd;
    for (int i = 0; i < PROCESS_NUM; i++)
    {
        int fds[2];
        int n = pipe(fds);
        assert(n == 0);
        (void)n; // release版本assert不会执行，没有使用n回报警告，人畜无害的代码

        pid_t id = fork(); // 父进程在for循环里面不断fork创建子进程
        //父进程打开的文件，是会被子进程共享的
        if (id == 0)
        {
            if(!deleteFd.empty())//这里也可以不用判断，因为for循环内部当deleteFd中有wfd时才会进入循环内部。
            {
                for(int i = 0; i<deleteFd.size(); i++) 
                {
                    close(deleteFd[i]);
                }
            }
            // 子进程，进行处理任务
            close(fds[1]);
            // 想要每个子进程的id还有其files_struct中指向管道的fd，将他们存放起来
            while (true)
            {
                // 1.获取命令码，父进程没有写入，则子进程阻塞等待
                int commandcode = recvTask(fds[0]);
                // 2.读取到任务码，然后完成任务
                if (commandcode >= 0 && commandcode < funcMap.size())
                    funcMap[commandcode]();
                else if (commandcode == -1)
                    break; // 如果任务码(写端不写了，recv返回-1)为-1，则当前子进程不去完成任务了，退出即可。如果其他子进程的任务码也是-1，同理。
                // std::cout << "subprocess recv commandcode error!" << std::endl;
            }
            exit(0);
        }
        close(fds[0]); // 关闭父进程的5个管道文件的读端，只保留每个管道文件的写端
        subEp sub(id, fds[1]);//定义出一个subEd对象，这个对象主要包含子进程的pid以及当前子进程和父进程的管道wfd
        out->push_back(sub);//将subEd对象放到vector里面
        deleteFd.push_back(fds[1]);
    }
}
void sendTask(const subEp &process, int taskNum)
{
    // 输出一条提示信息，表示父进程已经派发任务给子进程
    std::cout << "send task num: " << taskNum << " send to -> " << process._name << std::endl;
    ssize_t n = write(process._writefd, &taskNum, sizeof(taskNum)); // 向管道写一个command code，让子进程读取并执行
    assert(n == sizeof(int));
    (void)n;
}
void loadBlanceControl(const std::vector<subEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    int processnum = subs.size();
    int tasknum = funcMap.size();
    bool forever = count == 0 ? true : false;
    while (true) // 不退出就一直控制子进程
    {
        //让子进程负载均衡---随机数法+模运算
        // 1.随机选择一个子进程 -->std::vector<subEp>-->index
        int subIdx = rand() % processnum;
        // 2.随机选择一个任务 -->std::vector<func_t>-->index
        int taskIdx = rand() % tasknum;
        // 3.将选择的任务派发给选择的子进程
        sendTask(subs[subIdx], taskIdx);
        // sleep(1);
        if (!forever)//如果forever是真，那就什么都不做，继续while循环让父进程发送任务，永远控制子进程。如果是假，则需要控制发送任务的次数。
        {
            count--;
            if (count == 0) break;
        }
    }
    // 写端wfd关闭 -> 读端rfd读到0
    for (int i = 0; i < processnum; i++) close(subs[i]._writefd);//waitpid(subs[i].subID, nullptr, 0)，如果你加上这句，程序就会崩溃。
     // 关闭打开的所有管道的写端描述符
}


void waitProcess(std::vector<subEp>& processes)
{
    int processnum = processes.size(); // 计算出进程的个数，防止.size()函数被频繁调用
    for (int i = 0; i < processnum; i++)
    {
        waitpid(processes[i]._subID, nullptr, 0); // 阻塞式等待
        std::cout << "wait sub process success...:" << processes[i]._subID << std::endl;
    }
}

int main()
{
    MakeSeed();
    // 1.建立子进程并建立和子进程进行通信的信道，有bug？
    // <子进程id，write fd>
    // 1.1 加载任务表
    std::vector<func_t> funcMap;
    loadTaskFunc(&funcMap);
    // 1.2 创建子进程，将子进程的id writefd name等维护到vector subs里面
    std::vector<subEp> subs;          // 这就叫先描述，再组织
    createsubprocess(&subs, funcMap); // 通过输出型参数的方式，将我们想要的结果通过函数带出到subs数组里面

    // 2.走到这里的就是父进程，控制子进程，负载均衡的控制，向子进程发送任务码
    int taskCnt = 0; // 0 --> 父进程永远控制子进程，>0则让子进程完成几件任务
    loadBlanceControl(subs, funcMap, taskCnt);

    // 3.回收子进程
    waitProcess(subs);

    return 0; // 父进程退出，关闭写端，子进程的读端会读到0
}
// while :; do ps axj | head -1 && ps axj | grep processpool | grep -v grep; sleep 1;done
